using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using Unity.VisualScripting;
using UnityEngine;
using Debug = UnityEngine.Debug;

public enum EvaluationFunctionType
{
    Manhattan, //曼哈顿距离
    Diagonal, //迪杰斯特拉
    Euclid, //欧几里得
}

public class AStar
{
    private Dictionary<int, AStarNode> grideDic = new Dictionary<int, AStarNode>();

    public List<AStarNode> closeList = new List<AStarNode>(); //闭集合
    public List<AStarNode> openList = new List<AStarNode>(); //开集合

    public List<AStarNode> tempList = new List<AStarNode>(); //每次查找的目标点周围8个点

    public EvaluationFunctionType m_evaluationFunctionType;

    private AStarNode starNode, endNode;

    static int FACTOR = 10; //水平竖直相邻格子的距离
    static int FACTOR_DIAGONAL = 14; //对角线相邻格子的距离

    public IEnumerator FindPath(List<AStarNode> grideList, Vector2Int startPoint, Vector2Int endPoint,EvaluationFunctionType EvaluationFunctionType, Action<bool> callBack)
    {
        closeList.Clear();
        openList.Clear();
        tempList.Clear();

        grideDic.Clear();
        foreach (var node in grideList)
        {
            int key = node.pos.x * 1000000 + node.pos.y;
            grideDic.Add(key, node);
        }

        this.m_evaluationFunctionType = EvaluationFunctionType;

        starNode = GetNodeByPos(startPoint.x, startPoint.y);
        endNode = GetNodeByPos(endPoint.x, endPoint.y);
        var curNode = starNode;

        if (starNode == null)
        {
            Debug.LogError("起始点不在 map 内");
            callBack?.Invoke(false);
            yield break;
        }

        if (endNode == null)
        {
            Debug.LogError("终点点不在 map 内");
            callBack?.Invoke(false);
            yield break;
        }

        starNode.G = starNode.H = 0;

        openList.Add(curNode);

        while (!openList.Contains(endNode))
        {
            openList.Remove(curNode);
            closeList.Add(curNode);
            
            GetAroundNode(curNode.pos);

            foreach (var node in tempList)
            {
                if (openList.Contains(node))
                {
                    //如果开集合已经包含 则计算并比较 G 值
                    //如果当前 G 值更小则更新 G 值并更改父节点指针指向当前节点，否则什么都不变
                    var g = CalcG(curNode, node);
                    if (g < node.G)
                    {
                        node.parent = curNode;
                        node.G = g;
                        map.Instance.SetGHF(node,node.G,node.H,node.F,curNode.pos-node.pos,true);
                    }
                }
                else
                {
                    //计算 G，H值加入开集合
                    CalcGH(curNode, node);
                    node.parent = curNode;
                    openList.Add(node);
                    map.Instance.DrawNodeColor(node,Color.yellow);
                    map.Instance.SetGHF(node,node.G,node.H,node.F,curNode.pos-node.pos,false);
                    yield return new WaitForSeconds(0.1f);
                }
            }


            if (openList.Count == 0)
            {
                Debug.LogError("未查找到路径！");
                callBack?.Invoke(false);
                yield break;
            }

            //找到开集合最小的点，设置为当前节点，从开集合移除并加入到闭集合
            openList.Sort((a, b) => a.F.CompareTo(b.F));
            curNode = openList[0];
            yield return new WaitForSeconds(0.1f);
        }

        callBack?.Invoke(true);
    }

    /// <summary>
    /// 计算 G 值
    /// </summary>
    /// <param name="curPoint"></param>
    /// <param name="calcPoint"></param>
    private int CalcG(AStarNode curPoint, AStarNode calcPoint)
    {
        if (curPoint.pos.x == calcPoint.pos.x || curPoint.pos.y == calcPoint.pos.y)
        {
            return curPoint.G + FACTOR;
        }
        else
        {
            return curPoint.G + FACTOR_DIAGONAL;
        }
    }

    /// <summary>
    /// 计算 GH 值
    /// </summary>
    /// <param name="curPoint"></param>
    /// <param name="calcPoint"></param>
    /// <returns></returns>
    private void CalcGH(AStarNode curPoint, AStarNode calcPoint)
    {
        calcPoint.G = CalcG(curPoint,calcPoint);
        calcPoint.H = GetH(calcPoint.pos);
    }

    //获取估价距离
    private int GetH(Vector2Int position)
    {
        if (m_evaluationFunctionType == EvaluationFunctionType.Manhattan)
            return GetManhattanDistance(position);
        else if (m_evaluationFunctionType == EvaluationFunctionType.Diagonal)
            return GetDiagonalDistance(position);
        else
            return Mathf.CeilToInt(GetEuclideanDistance(position));
    }

    //获取对角线距离
    private int GetDiagonalDistance(Vector2Int position)
    {
        int x = Mathf.Abs(endNode.pos.x - position.x);
        int y = Mathf.Abs(endNode.pos.y - position.y);
        int min = Mathf.Min(x, y);
        return min * FACTOR_DIAGONAL + Mathf.Abs(x - y) * FACTOR;
    }

    //获取曼哈顿距离
    int GetManhattanDistance(Vector2Int position)
    {
        return Mathf.Abs(endNode.pos.x - position.x) * FACTOR + Mathf.Abs(endNode.pos.y - position.y) * FACTOR;
    }

    //获取欧几里得距离,测试下来并不合适
    float GetEuclideanDistance(Vector2Int position)
    {
        return Mathf.Sqrt(Mathf.Pow((endNode.pos.x - position.x) * FACTOR, 2) +
                          Mathf.Pow((endNode.pos.y - position.y) * FACTOR, 2));
    }

    /// <summary>
    /// 获取周围节点
    /// </summary>
    /// <param name="point"></param>
    /// <returns></returns>
    private void GetAroundNode(Vector2Int point)
    {
        tempList.Clear();
        var lu = GetNodeByPos(point.x - 1, point.y + 1); //左上
        var up = GetNodeByPos(point.x, point.y + 1); //上
        var ru = GetNodeByPos(point.x + 1, point.y + 1); //右上
        var left = GetNodeByPos(point.x - 1, point.y); //左
        var right = GetNodeByPos(point.x + 1, point.y); //右
        var ld = GetNodeByPos(point.x - 1, point.y - 1); //左下
        var down = GetNodeByPos(point.x, point.y - 1); //下
        var rd = GetNodeByPos(point.x + 1, point.y - 1); //右下

        if (lu != null && !lu.isBlock && !closeList.Contains(lu)) tempList.Add(lu);
        if (up != null && !up.isBlock && !closeList.Contains(up)) tempList.Add(up);
        if (ru != null && !ru.isBlock && !closeList.Contains(ru)) tempList.Add(ru);
        if (left != null && !left.isBlock && !closeList.Contains(left)) tempList.Add(left);
        if (right != null && !right.isBlock && !closeList.Contains(right)) tempList.Add(right);
        if (ld != null && !ld.isBlock && !closeList.Contains(ld)) tempList.Add(ld);
        if (down != null && !down.isBlock && !closeList.Contains(down)) tempList.Add(down);
        if (rd != null && !rd.isBlock && !closeList.Contains(rd)) tempList.Add(rd);
    }

    public AStarNode GetNodeByPos(int x, int y)
    {
        int key = x * 1000000 + y;
        grideDic.TryGetValue(key, out AStarNode node);

        return node;
    }
}